use darling::{FromDeriveInput, FromMeta};
use proc_macro::TokenStream;
use proc_macro2::TokenStream as T2;
use quote::{format_ident, quote};
use syn::{parse_macro_input, DeriveInput};

use crate::periph2::rcc::{self, RccAxB};

#[derive(Debug, FromDeriveInput)]
#[darling(attributes(dma))]
pub struct DmaConfig {
    init: String,
    #[darling(multiple)]
    ch: Vec<DmaAttr>,
}

impl DmaConfig {
    pub fn build(&self) -> T2 {
        let mut dma1_en = false;
        let mut dma2_en = false;
        let mut expand = quote! {};
        for ch in self.ch.iter() {
            if ch.dma == 1 {
                dma1_en = true;
            } else if ch.dma == 2 {
                dma2_en = true;
            }
            expand.extend(ch.build());
        }
        let mut rcc_field = Vec::new();
        if dma1_en {
            rcc_field.push(format_ident!("dma1en"));
        }
        if dma2_en {
            rcc_field.push(format_ident!("dma2en"));
        }
        let init_fn = format_ident!("{}", self.init);
        let rcc_src = rcc::rcc_enable_all(RccAxB::AHB, &rcc_field, false);

        quote! {
            pub fn #init_fn() {
                #rcc_src
            }
            #expand
        }
    }
}

#[derive(Debug, FromMeta)]
pub struct DmaAttr {
    name: String,
    dma: u8,
    ch: u8,
    size: u8,
    dir: Dir,
    #[darling(default)]
    circ: bool,
    #[darling(default)]
    tcie: bool,
}

impl DmaAttr {
    fn build(&self) -> T2 {
        let build_fn = match self.dir {
            Dir::Read => format_ident!("{}_dma{}_ch{}_read", self.name, self.dma, self.ch),
            Dir::Write => format_ident!("{}_dma{}_ch{}_write", self.name, self.dma, self.ch),
        };

        let dma = format_ident!("DMA{}", self.dma);
        let cteif = format_ident!("cteif{}", self.ch);
        let chtif = format_ident!("chtif{}", self.ch);
        let ctcif = format_ident!("ctcif{}", self.ch);
        let cgif = format_ident!("cgif{}", self.ch);
        let ch = format_ident!("ch{}", self.ch);

        let size = format_ident!("bits{}", self.size);
        let circ = if self.circ {
            format_ident!("enabled")
        } else {
            format_ident!("disabled")
        };
        let dir = match self.dir {
            Dir::Read => format_ident!("from_peripheral"),
            Dir::Write => format_ident!("from_memory"),
        };
        let tcie = if self.tcie {
            format_ident!("enabled")
        } else {
            format_ident!("disabled")
        };

        let mut expand = quote! {
            pub fn #build_fn(mar: u32, par: u32, count: u16) {
                let dma: stm32f1::stm32f103::#dma = unsafe { ::core::mem::transmute(()) };
                dma.ifcr.write(|w| w.#cteif().clear().#chtif().clear().#ctcif().clear().#cgif().clear());
                dma.#ch.cr.reset();
                dma.#ch.mar.write(|w| w.ma().variant(mar));
                dma.#ch.par.write(|w| w.pa().variant(par));
                dma.#ch.ndtr.write(|w| w.ndt().variant(count));
                dma.#ch.cr.write(|w|
                    w.mem2mem()
                    .disabled()
                    .pl()
                    .low()
                    .msize()
                    .#size()
                    .psize()
                    .#size()
                    .minc()
                    .enabled()
                    .pinc()
                    .disabled()
                    .circ()
                    .#circ()
                    .dir()
                    .#dir()
                    .teie()
                    .disabled()
                    .htie()
                    .disabled()
                    .tcie()
                    .#tcie()
                    .en()
                    .enabled()
                )
            }
        };
        if !self.circ {
            expand.extend( self.build_is_tc() );
        }
        match self.dir {
            Dir::Write => {
                expand.extend( self.build_clear_flags() );
                expand.extend( self.build_ndtr() );
            },
            Dir::Read => {
                expand.extend( self.build_ndtr() );
            }
        };
        expand
    }

    fn build_clear_flags(&self) -> T2 {
        let build_fn = format_ident!("{}_dma{}_ch{}_clear_flags", self.name, self.dma, self.ch);
        let dma = format_ident!("DMA{}", self.dma);
        let cteif = format_ident!("cteif{}", self.ch);
        let chtif = format_ident!("chtif{}", self.ch);
        let ctcif = format_ident!("ctcif{}", self.ch);
        let cgif = format_ident!("cgif{}", self.ch);
        quote! {
            pub fn #build_fn() {
                let dma: stm32f1::stm32f103::#dma = unsafe { ::core::mem::transmute(()) };
                dma.ifcr.write(|w| w.#cteif().clear().#chtif().clear().#ctcif().clear().#cgif().clear());
            }
        }
    }

    fn build_ndtr(&self) -> T2 {
        let build_fn = format_ident!("{}_dma{}_ch{}_ndtr", self.name, self.dma, self.ch);
        let dma = format_ident!("DMA{}", self.dma);
        let ch = format_ident!("ch{}", self.ch);
        quote! {
            pub fn #build_fn() -> u16 {
                let dma: stm32f1::stm32f103::#dma = unsafe { ::core::mem::transmute(()) };
                dma.#ch.ndtr.read().ndt().bits()
            }
        }
    }

    fn build_is_tc(&self) -> T2 {
        let build_fn = format_ident!("{}_dma{}_ch{}_is_tc", self.name, self.dma, self.ch);
        let dma = format_ident!("DMA{}", self.dma);
        let tcif = format_ident!("tcif{}", self.ch);
        quote! {
            pub fn #build_fn() -> bool {
                let dma: stm32f1::stm32f103::#dma = unsafe { ::core::mem::transmute(()) };
                dma.isr.read().#tcif().is_complete()
            }
        }
    }
}

#[derive(Debug, FromMeta)]
pub enum Dir {
    #[darling(rename = "read")]
    Read,
    #[darling(rename = "write")]
    Write,
}

pub fn build(input: TokenStream) -> TokenStream {
    let input = parse_macro_input!(input as DeriveInput);
    let name = &input.ident;
    let dma_config = DmaConfig::from_derive_input(&input).unwrap();
    let build = dma_config.build();
    let expand = quote! {
        impl #name {
            #build
        }
    };
    expand.into()
}
